package main

import (
	"fmt"
	"net"
	"strings"
	"time"
)

type User struct {
	// 名字
	name string
	// 唯一id
	id string
	// 管道
	msg chan string
}

// 创建全局map结构，保存所有用户，用指针，是为了，后面用户改名的时候，不用重新再往allUsers进行覆盖
var allUsers = make(map[string]*User)

// 定义应该message全局通道，用于接收任何人发送过来消息
var message = make(chan string, 10)

func main() {
	// 监听
	listener, err := net.Listen("tcp", ":8088")
	if err != nil {
		fmt.Println("net.Listen err: ", err)
		return
	}

	// 启动全局唯一的go程，负责监听message通道，写个所有用户
	go broadcast()

	fmt.Println("服务器启动成功!")

	for {
		fmt.Println("主go程监听中...")

		// 连接
		conn, err := listener.Accept()
		if err != nil {
			fmt.Println("listener.Accept err: ", err)
			return
		}

		fmt.Println("连接建立成功,准备接收客户端数据")
		// 读客户端数据
		go handle(conn)
	}
}

// 读取客户端发来的数据，进行处理
func handle(conn net.Conn) {

	fmt.Println("启动业务")

	// 客户端与服务器建立连接的时候，会有ip和port ===》 当成user的id
	clientAddr := conn.RemoteAddr().String()
	fmt.Println("clientAddr: ", clientAddr)
	// 创建user
	newUser := User{
		id:   clientAddr,            // id, 我们不会修改，这个作为map的key
		name: clientAddr,            // 可以修改，会提供rename命令修改，初始化和id相同
		msg:  make(chan string, 10), // 需要make空间，否则无法写入数据
	}
	// 启动go程，负责将msg消息返回给客户端
	go writeBackClient(&newUser, conn)
	// 添加user到map结构
	allUsers[newUser.id] = &newUser

	// 定义一个退出信号，用于监听client退出
	var isQUit = make(chan bool)
	// 启动go程，负责监听退出信号
	go watch(&newUser, conn, isQUit)

	// 向message写入数据，当前用户上线的消息，用于通知所有人（广播）
	loginInfo := fmt.Sprintf("[%s]:[%s] ======> 上线了login!!\n", newUser.id, newUser.name)
	message <- loginInfo

	chatState := make(chan bool)

	go func() {
		for {

			buf := make([]byte, 1024)

			cnt, err := conn.Read(buf)
			if cnt == 0 {
				fmt.Println("客户端主动关闭ctrl+c, 准备退出")
				// 清理map删除用户, conn close掉
				// 在这里不进行真正的退出动作，发送一个退出信号，统一作退出处理，可以使用管道作信号传递
				isQUit <- true
			}
			if err != nil {
				fmt.Println("conn.Read err: ", err)
				return
			}
			fmt.Println("接收的数据为: ", string(buf[:cnt-1]), ", 长度为: ", cnt)

			// --------------------- 具体业务逻辑 开始 ---------------
			// 1、查询当前所有用户 who
			// 		a、判断接收的数据是不是who  ====》 长度&&字符串
			userInput := string(buf[:cnt-1]) // 用户输入的数据
			if len(userInput) == 4 && userInput == "\\who" {
				// 		b、遍历allUsers,将id和name拼接成一个字符串,返回给客户
				fmt.Println("用户查询有哪些用户")

				// 这个数组切片包含所有的用户信息
				var userInfos []string

				for _, user := range allUsers {
					userInfo := fmt.Sprintf("userid: %s, username: %s", user.id, user.name)
					userInfos = append(userInfos, userInfo)
				}

				// 最终写到管道中，是字符串
				r := strings.Join(userInfos, "\n")
				newUser.msg <- r

			} else if len(userInput) > 9 && userInput[:7] == "\\rename" {
				// 输入风格为\rename|名字 ,分割|
				newUser.name = strings.Split(userInput, "|")[1]
				// 通知客户端改名成功
				newUser.msg <- "rename successfully!!"

			} else {
				//  如果用户不是命令，则存入广播
				message <- userInput
			}
			chatState <- true
			// --------------------- 具体业务逻辑 结束 ---------------
		}
	}()

	for {
		select {
		case <-time.After(5 * time.Second):
			isQUit <- true
		case <-chatState:
		}
	}
}

// 向所有的用户广播消息，启动一个全局唯一go程
func broadcast() {
	fmt.Println("广播go程启动成功...")
	defer fmt.Println("broadcast 程序退出")

	for {
		// 1、从message中读取数据
		info := <-message

		fmt.Println("message接收到消息: ", info)
		// 2、将数据写入到每一个用户的msg管道
		for _, user := range allUsers {
			// 如果msg非缓冲的，那么会阻塞在这里
			user.msg <- info
		}
	}
}

// 每个用户应该还有一个用来监听自己msg管道的go程，负责将数据返回给客户端
func writeBackClient(user *User, conn net.Conn) {
	fmt.Printf("user: %s 的go程正在监听msg管道\n", user.name)
	for data := range user.msg {
		fmt.Printf("User : %s 写回给客户端的数据为: %s\n", user.name, data)
		// 写回客户端
		conn.Write([]byte(data + "\n"))
	}
}

// 启动一个go程，负责监听退出信号，触发后，进行清零工作：delete map, close conn
func watch(user *User, conn net.Conn, isQuit chan bool) {
	fmt.Println("启动监听退出信号的go程")
	for {
		select {
		case <-isQuit:
			logoutInfo := fmt.Sprintf("%s exit already!", user.name)
			fmt.Println("删除当前用户: ", user.name)
			delete(allUsers, user.id)
			message <- logoutInfo
			conn.Close()
			return
		}
	}
}
